package org.jiucheng.magpiebridge.manager.controller.openapi.v1;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;

import org.jiucheng.ioc.annotation.Inject;
import org.jiucheng.magpiebridge.manager.dto.BlackPortsDto;
import org.jiucheng.magpiebridge.manager.dto.openapi.v1.ClientMappingReqDto;
import org.jiucheng.magpiebridge.manager.dto.openapi.v1.ClientReqDto;
import org.jiucheng.magpiebridge.manager.dto.openapi.v1.Openapi;
import org.jiucheng.magpiebridge.manager.entity.Client;
import org.jiucheng.magpiebridge.manager.entity.ClientMapping;
import org.jiucheng.magpiebridge.manager.entity.Server;
import org.jiucheng.magpiebridge.manager.entity.ServerResource;
import org.jiucheng.magpiebridge.manager.entity.ServerResourceRule;
import org.jiucheng.magpiebridge.manager.handler.OpenapiHandler;
import org.jiucheng.magpiebridge.manager.handler.RequestBody;
import org.jiucheng.magpiebridge.manager.out.OpenapiOut;
import org.jiucheng.magpiebridge.manager.service.IClientService;
import org.jiucheng.magpiebridge.manager.service.IServerResourceService;
import org.jiucheng.magpiebridge.manager.util.ServerNotify;
import org.jiucheng.magpiebridge.manager.util.ThreadArgs;
import org.jiucheng.plugin.db.IBaseService;
import org.jiucheng.util.StringUtil;
import org.jiucheng.web.annotation.Controller;
import org.jiucheng.web.annotation.RequestMapping;
import org.jiucheng.web.annotation.RequestMethod;

@Controller(OpenapiHandler.class)
public class ClientMappingController {
    
    @Inject
    private IBaseService baseService;
    @Inject
    private IServerResourceService serverResourceService;
    @Inject
    private IClientService clientService;
    
    @RequestMapping(value = "/openapi/v1/client/mapping/edit", method = RequestMethod.POST, out = OpenapiOut.class)
    public Openapi edit(@RequestBody ClientMappingReqDto reqDto) {
        if (reqDto == null) 
            return Openapi.newInstance(1, "请求内容不能为空");
        if (reqDto.getId() == null)
            return Openapi.newInstance(2, "编号不能为空");
        if (StringUtil.isBlank(reqDto.getLocalIpPort()))
            return Openapi.newInstance(3, "内网不能为空");
        ClientMapping clientMapping = baseService.get(ClientMapping.class, reqDto.getId());
        if (clientMapping == null)
            return Openapi.newInstance(4, "映射不存在");
        clientMapping.setLocalIpPort(reqDto.getLocalIpPort());
        baseService.update(clientMapping);
        Server server = baseService.get(Server.class, clientMapping.getServerId());
        Client client = baseService.get(Client.class, clientMapping.getClientId());
        if (server != null && client != null)
            ServerNotify.notify(server, client.getToken());
        return Openapi.newInstance();
    }
    
    @RequestMapping(value = "/openapi/v1/client/mapping/del", method = RequestMethod.POST, out = OpenapiOut.class)
    public Openapi del(@RequestBody ClientReqDto reqDto) {
        if (reqDto == null) 
            return Openapi.newInstance(1, "请求内容不能为空");
        if (StringUtil.isBlank(reqDto.getIds()))
            return Openapi.newInstance(2, "编号不能为空");
        Map<Long, Set<String>> serverIdAndTokens = new HashMap<Long, Set<String>>();
        List<ClientMapping> needDelete = new ArrayList<ClientMapping>();
        String[] idsStr = reqDto.getIds().split(",");
        for (String idStr : idsStr) {
            if (StringUtil.isNotBlank(idStr) && idStr.matches("[0-9]+")) {
                ClientMapping clientMapping = baseService.get(ClientMapping.class, Long.valueOf(idStr));
                if (clientMapping != null) {
                    Client client = baseService.get(Client.class, clientMapping.getClientId());
                    if (client != null) {
                        if (!client.getUserId().equals(ThreadArgs.getGid()))
                            return Openapi.newInstance(4, "非法操作");
                        Set<String> tokens = serverIdAndTokens.get(client.getServerId());
                        if (tokens == null) {
                            tokens = new HashSet<String>();
                            serverIdAndTokens.put(client.getServerId(), tokens);
                        }
                        tokens.add(client.getToken());
                    }
                    needDelete.add(clientMapping);
                }
            }
        }
        clientService.deleteClientMappings(needDelete);
        for (Entry<Long, Set<String>> entry : serverIdAndTokens.entrySet()) {
            try {
                Server server = baseService.get(Server.class, entry.getKey());
                if (server != null) {
                    for (String token : entry.getValue()) {
                        ServerNotify.notify(server, token);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return Openapi.newInstance();
    }
    
    @RequestMapping(value = "/openapi/v1/client/mapping/add", method = RequestMethod.POST, out = OpenapiOut.class)
    public Openapi add(@RequestBody ClientMappingReqDto reqDto) {
        if (reqDto == null) {
            return Openapi.newInstance(1, "请求内容不能为空");
        }
        if (reqDto.getClientId() == null) {
            return Openapi.newInstance(2, "隧道不能为空");
        }
        if (StringUtil.isBlank(reqDto.getLocalIpPort())) {
            return Openapi.newInstance(3, "内网不能为空");
        }
        Client client = baseService.get(Client.class, reqDto.getClientId());
        if (client == null) {
            return Openapi.newInstance(4, "隧道不存在");
        }
        if (!client.getUserId().equals(ThreadArgs.getGid())) {
            return Openapi.newInstance(4, "非法操作");
        }
        Server server = baseService.get(Server.class, client.getServerId());
        if (server == null) {
            return Openapi.newInstance(5, "服务器不存在");
        }
        
        // 校验协议
        if (server.getProtocolType() != null && server.getProtocolType() != 0) {
            if (!"TCP".equalsIgnoreCase(reqDto.getProtocol())) {
            	// HTTP
            	if (server.getProtocolType() != 1) {
            		return Openapi.newInstance(6, "服务器配置协议：HTTP协议不支持");
            	}
            } else {
            	// TCP
            	if (server.getProtocolType() != 1) {
            		return Openapi.newInstance(7, "服务器配置协议：TCP协议不支持");
            	}
            }
        }
        
        ServerResourceRule serverResourceRule = new ServerResourceRule();
        serverResourceRule.setServerId(server.getId());
        List<ServerResourceRule> serverResourceRules = baseService.list(serverResourceRule);
        if (serverResourceRules != null && !serverResourceRules.isEmpty()) {
            serverResourceRule = serverResourceRules.get(0);
        } else {
            serverResourceRule.setCurrentPort(10000);
            serverResourceRule.setCurrentSub(Long.toString(65535, Character.MAX_RADIX));
            serverResourceRule.setId((Long) baseService.save(serverResourceRule));
        }
        ClientMapping clientMapping = new ClientMapping();
        ServerResourceRule srr = null;
        if (!"TCP".equalsIgnoreCase(reqDto.getProtocol())) {
            String currentSub = Long.toString(Long.parseLong(serverResourceRule.getCurrentSub(), Character.MAX_RADIX) + 1L, Character.MAX_RADIX);
            String blackSubs = Objects.toString(server.getBlackSubs(), StringUtil.EMPTY).toLowerCase();
            if (StringUtil.isNotBlank(blackSubs)) {
                blackSubs = "," + blackSubs + ",";
                while (blackSubs.indexOf(currentSub) > -1) {
                    currentSub = Long.toString(Long.parseLong(currentSub, Character.MAX_RADIX) + 1L, Character.MAX_RADIX);
                }
            }
            ServerResourceRule db = new ServerResourceRule();
            db.setCurrentSub(currentSub);
            db.setId(serverResourceRule.getId());
            // baseService.update(db);
            srr = db;
            clientMapping.setServerProtocol("HTTP");
            clientMapping.setServerIpPort(currentSub);
        } else {
            int lastPort = lastAvailResourcePort(server.getId(), server.getBlackPorts(), server.getPort());
            if (lastPort == -1) {
                int currentPort = serverResourceRule.getCurrentPort() + 1;
                currentPort = checkPortAndDb(currentPort, server.getBlackPorts(), server.getPort(), client);
                if (currentPort < 0 || currentPort > 65535) {
                    return Openapi.newInstance(6, "服务器无端口可分配");
                }
                ServerResourceRule db = new ServerResourceRule();
                db.setCurrentPort(currentPort);
                db.setId(serverResourceRule.getId());
                // baseService.update(db);
                srr = db;
                clientMapping.setServerIpPort(Integer.toString(currentPort));
            } else {
                clientMapping.setServerIpPort(Integer.toString(lastPort));
            }
            clientMapping.setServerProtocol("TCP");
        }
        clientMapping.setClientId(client.getId());
        clientMapping.setServerId(client.getServerId());
        clientMapping.setLocalIpPort(reqDto.getLocalIpPort());
        // Serializable id = baseService.save(clientMapping);
        Serializable id = clientService.saveClientMappingAndUpdateServerResourceRule(clientMapping, srr);
        ServerNotify.notify(server, client.getToken());
        return Openapi.newInstance(id);
    }
    
    private int lastAvailResourcePort(Long serverId, String blackPorts, int serverPort) {
        ServerResource serverResource = serverResourceService.lastServerResource(serverId, "TCP");
        if (serverResource == null)
            return -1;
        int port = Integer.parseInt(serverResource.getServerIpPort());
        ServerResource delete = new ServerResource();
        delete.setId(serverResource.getId());
        baseService.delete(delete);
        if (port == serverPort)
            return lastAvailResourcePort(serverId, blackPorts, serverPort);  
        if (StringUtil.isBlank(blackPorts))
            return port;
        String[] portss = blackPorts.split(",");
        List<BlackPortsDto> blackPortsDtos = new ArrayList<BlackPortsDto>();
        for (String ports : portss) {
            if (StringUtil.isNotBlank(ports)) {
                String[] sport = ports.split("-");
                if (sport.length == 1) {
                    if (sport[0].matches("[0-9]+")) {
                        int port1 = Integer.parseInt(sport[0]);
                        if (port1 >= 0 && port1 < 65536) {
                            blackPortsDtos.add(new BlackPortsDto(port1, port1));
                        }
                    }
                } else if (sport.length == 2) {
                    if (sport[0].matches("[0-9]+") && sport[1].matches("[0-9]+")) {
                        int port1 = Integer.parseInt(sport[0]);
                        int port2 = Integer.parseInt(sport[1]);
                        if (port1 <= port2 && port1 < 65536 && port1 >= 0) {
                            blackPortsDtos.add(new BlackPortsDto(port1, port2));
                        }
                    }
                }
            }
        }
        if (blackPortsDtos.isEmpty())
            return port;
        Collections.sort(blackPortsDtos);
        for (BlackPortsDto blackPortsDto : blackPortsDtos) {
            if (blackPortsDto.getStartPort() <= port && port <= blackPortsDto.getEndPort())
                return lastAvailResourcePort(serverId, blackPorts, serverPort);
        }
        return port;
    }
    
    private int checkPortAndDb(int currentPort, String blackPorts, int serverPort, Client client) {
        int port = checkPort(currentPort, blackPorts, serverPort);
        if (port < 0 || port > 65535) {
            return port;
        }
        ClientMapping clientMapping = new ClientMapping();
        clientMapping.setClientId(client.getId());
        clientMapping.setServerId(client.getServerId());
        clientMapping.setServerIpPort(Integer.toString(port));
        clientMapping.setServerProtocol("TCP");
        List<ClientMapping> list = baseService.list(clientMapping);
        if (list == null || list.isEmpty())
            return port;
        return checkPortAndDb(port + 1, blackPorts, serverPort, client);
    }
    
    private int checkPort(int currentPort, String blackPorts, int serverPort) {
        if (currentPort == serverPort)
            return checkPort(currentPort + 1, blackPorts, serverPort);
        if (currentPort > 65535 || StringUtil.isBlank(blackPorts))
            return currentPort;
        String[] portss = blackPorts.split(",");
        List<BlackPortsDto> blackPortsDtos = new ArrayList<BlackPortsDto>();
        for (String ports : portss) {
            if (StringUtil.isNotBlank(ports)) {
                String[] sport = ports.split("-");
                if (sport.length == 1) {
                    if (sport[0].matches("[0-9]+")) {
                        int port1 = Integer.parseInt(sport[0]);
                        if (port1 >= 0 && port1 < 65536) {
                            blackPortsDtos.add(new BlackPortsDto(port1, port1));
                        }
                    }
                } else if (sport.length == 2) {
                    if (sport[0].matches("[0-9]+") && sport[1].matches("[0-9]+")) {
                        int port1 = Integer.parseInt(sport[0]);
                        int port2 = Integer.parseInt(sport[1]);
                        if (port1 <= port2 && port1 < 65536 && port1 >= 0) {
                            blackPortsDtos.add(new BlackPortsDto(port1, port2));
                        }
                    }
                }
            }
        }
        if (!blackPortsDtos.isEmpty())
            Collections.sort(blackPortsDtos);
        return checkPort1(currentPort, blackPortsDtos, serverPort);
    }
    
    private int checkPort1(int currentPort, List<BlackPortsDto> blackPortsDtos, int serverPort) {
        if (currentPort == serverPort)
            return checkPort1(currentPort + 1, blackPortsDtos, serverPort);
        if (currentPort > 65535 || blackPortsDtos.isEmpty()) {
            return currentPort;
        }
        BlackPortsDto blackPortsDto = blackPortsDtos.remove(0);
        if (blackPortsDto.getStartPort() <= currentPort && currentPort <= blackPortsDto.getEndPort())
            currentPort = blackPortsDto.getEndPort() + 1;
        return checkPort1(currentPort, blackPortsDtos, serverPort);
    }
}
